﻿//AcademicPlanner - course registration planning web app.
//Copyright (C) 2009  Boubacar Diallo

//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either version 2
//of the License, or (at your option) any later version.

//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AcademicPlanner.Framework.Helpers;
using AcademicPlanner.Data;
using AcademicPlanner.Data.Entities;
using AcademicPlanner.Data.Repositories;
using AcademicPlanner.Domain.Models.Collections;
using AcademicPlanner.Domain.Components;
using AcademicPlanner.Framework.Libraries.Validation;

namespace AcademicPlanner.Domain.Models
{
	/// <summary>
	/// AcademicPlan represents a dashboard to hold all the different
	/// components required for a using the AcademicPlanner application.
	/// </summary>
	/// <since>1.0</since>
	public class AcademicPlan : Model
    {
		/// <summary>
		/// Minimum value for the <c>CreditUnits</c> property of a AcademicPlan: <c>0</c>.
		/// </summary>
		/// <since>1.0</since>
		public static readonly double CREDITUNITS_MIN_VALUE = 0;

		/// <summary>
		/// Maximum value for the <c>CreditUnits</c> property of a AcademicPlan: <c>100</c>.
		/// </summary>
		/// <since>1.0</since>
		public static readonly double CREDITUNITS_MAX_VALUE = 100;

		/// <summary>
		/// The collection of <c>CourseDivision</c>s under this AcademicPlan.
		/// </summary>
		/// <since>1.0</since>
		protected CourseDivisionCollection divisions;

		/// <summary>
		/// The collection of <c>AcademicTerm</c>s under this AcademicPlan.
		/// </summary>
		/// <since>1.0</since>
		protected AcademicTermCollection terms;

		/// <summary>
		/// The collection of <c>Program</c>s under this AcademicPlan.
		/// </summary>
		/// <since>1.0</since>
		protected ProgramCollection programs;

		/// <summary>
		/// The collection of <c>Course</c>s under this AcademicPlan.
		/// </summary>
		/// <since>1.0</since>
		protected CourseCollection courses;

		/// <summary>
		/// The collection of <c>AcademicSchedule</c>s under this AcademicPlan.
		/// </summary>
		/// <since>1.0</since>
		protected AcademicScheduleCollection schedules;


		/// <summary>
		/// Gets the data layer repository for this AcademicPlan's underlying data representation.
		/// </summary>
		/// <since>1.0</since>
		protected override EntityRepository EntityRepo { get { return DomainMaster.DSAS.AcademicPlanEntityRepo; } }

		/// <summary>
		/// Gets this AcademicPlan's underlying data representation.
		/// </summary>
		/// <since>1.0</since>
		protected AcademicPlanEntity Entity { get { return (AcademicPlanEntity)entity; } }

		/// <summary>
		/// Gets the common number of credits for most academic courses in this AcademicPlan.
		/// </summary>
		/// <exception cref="T:AcademicPlanner.Domain.NotAuthorizedException">
		/// Thrown with error code <c>DomainErrorCode.M_NOT_AUTHORIZED_VIEW_ACADEMICPLAN_CREDITUNITS</c> 
		/// if the user is not authorized to see this property.
		/// </exception>
		/// <since>1.0</since>
		public double CreditUnits
		{
			get 
			{
				if (!DomainMaster.Auth.IsUserAuthorized(ACOperationName.VIEW_ACADEMICPLAN, this))
					throw new NotAuthorizedException(DomainErrorCode.M_NOT_AUTHORIZED_VIEW_ACADEMICPLAN_CREDITUNITS);
				return Entity.Credit_Unit; 
			}
		}

		/// <summary>
		/// Gets the collection of <c>CourseDivision</c>s under this AcademicPlan.
		/// </summary>
		/// <exception cref="T:AcademicPlanner.Domain.NotAuthorizedException">
		/// Thrown with error code <c>DomainErrorCode.M_NOT_AUTHORIZED_VIEW_ACADEMICPLAN_DIVISIONS</c> 
		/// if the user is not authorized to see this property.
		/// </exception>
		/// <since>1.0</since>
		public CourseDivisionCollection Divisions 
		{ 
			get 
			{
				if (!DomainMaster.Auth.IsUserAuthorized(ACOperationName.VIEW_ACADEMICPLAN, this))
					throw new NotAuthorizedException(DomainErrorCode.M_NOT_AUTHORIZED_VIEW_ACADEMICPLAN_DIVISIONS);
				SetDivisions();
				return divisions; 
			} 
		}

		/// <summary>
		/// Gets the collection of <c>AcademicTerm</c>s under this AcademicPlan.
		/// </summary>
		/// <exception cref="T:AcademicPlanner.Domain.NotAuthorizedException">
		/// Thrown with error code <c>DomainErrorCode.M_NOT_AUTHORIZED_VIEW_ACADEMICPLAN_TERMS</c> 
		/// if the user is not authorized to see this property.
		/// </exception>
		/// <since>1.0</since>
		public AcademicTermCollection Terms
		{
			get
			{
				if (!DomainMaster.Auth.IsUserAuthorized(ACOperationName.VIEW_ACADEMICPLAN, this))
					throw new NotAuthorizedException(DomainErrorCode.M_NOT_AUTHORIZED_VIEW_ACADEMICPLAN_TERMS);
				SetTerms();
				return terms;
			}
		}

		/// <summary>
		/// Gets the collection of <c>Program</c>s under this AcademicPlan.
		/// </summary>
		/// <since>1.0</since>
		public ProgramCollection Programs 
		{
			get
			{
				SetPrograms();
				return programs; 
			} 
		}

		/// <summary>
		/// Gets the collection of <c>Course</c>s under this AcademicPlan.
		/// </summary>
		/// <since>1.0</since>
		public CourseCollection Courses 
		{ 
			get 
			{
				SetCourses();
				return courses; 
			} 
		}

		/// <summary>
		/// Gets the collection of <c>AcademicSchedule</c>s under this AcademicPlan.
		/// </summary>
		/// <since>1.0</since>
		public AcademicScheduleCollection Schedules
		{
			get
			{
				SetSchedules();
				return schedules;
			}
		}


		/// <summary>
		/// Constructs a new AcademicPlan.
		/// </summary>
		/// <remarks>
		/// This constructor invokes the matching super constructor.
		/// </remarks>
		/// <since>1.0</since>
		public AcademicPlan()
			: base()
		{
			
		}

		/// <summary>
		/// Constructs a new AcademicPlan with the provided <c>AcademicPlanEntity</c>.
		/// </summary>
		/// <remarks>
		/// This constructor invokes the matching super constructor.
		/// </remarks>
		/// <param name="pEntity">The AcademicTerm's <c>AcademicPlanEntity</c>.</param>
		/// <since>1.0</since>
		public AcademicPlan(AcademicPlanEntity pEntity) 
			: base(pEntity)
		{
			
		}


		private void ResetDivisions()
		{
			divisions = new CourseDivisionCollection(Entity.DivisionEntities);
		}

		private void ResetTerms()
		{
			terms = new AcademicTermCollection(Entity.TermEntities);
		}

		private void ResetCourses()
		{
			courses = new CourseCollection(Entity.CourseEntities);
		}

		private void ResetPrograms()
		{
			programs = new ProgramCollection(Entity.ProgramEntities);
		}

		private void ResetSchedules()
		{
			schedules = new AcademicScheduleCollection(Entity.ScheduleEntities);
		}

		private void SetCourses()
		{
			if (courses == null)
				ResetCourses();
		}

		private void SetSchedules()
		{
			if (schedules == null)
				ResetSchedules();
		}

		private void SetDivisions()
		{
			if (divisions == null)
				ResetDivisions();
		}

		private void SetTerms()
		{
			if (terms == null)
				ResetTerms();
		}

		private void SetPrograms()
		{
			if (programs == null)
				ResetPrograms();
		}

		/// <summary>
		/// Refreshes this AcademicPlan's <c>AcademicPlanEntity</c>
		/// by reloading it from the persistence layer.
		/// </summary>
		/// <since>1.0</since>
		public override void Refresh()
		{
			base.Refresh();
			ResetCourses();
		}

		/// <summary>
		/// Gets the <c>CourseCategory</c>s under this AcademicPlan;
		/// i.e the combination of <c>CourseCategory</c>s of all of
		/// its <c>Divisions</c>.
		/// </summary>
		/// <returns>The collection of <c>CourseCategory</c>s.</returns>
		/// <since>1.0</since>
		public IList<CourseCategory> GetCategories()
		{
			var rList = new List<CourseCategory>();

			SetDivisions();
			foreach (var vDiv in divisions.ToList())
			{
				rList = new List<CourseCategory>(rList.Concat(vDiv.Categories.ToList()));
			}

			return rList;
		}

		/// <summary>
		/// Gets <c>Course</c>s under this AcademicPlan with the given identifiers.
		/// </summary>
		/// <remarks>
		/// <c>Course</c>s with valid identifiers but do not
		/// belong to this AcademicPlan are excluded.
		/// </remarks>
		/// <param name="pEntityIds">The <c>Course</c>s identifiers.</param>
		/// <returns>The collection of <c>Course</c>s.</returns>
		/// <since>1.0</since>
		public IList<Course> GetCoursesWithId(IList<int> pEntityIds)
		{
			var rCrses = new List<Course>();

			var vQuery = from pCourse in Courses.ToList()
						 where pEntityIds.Contains(pCourse.EntityId)
						 select pCourse;

			rCrses = vQuery.ToList();

			return rCrses;
		}

		/// <summary>
		/// Gets <c>Course</c> under this AcademicPlan with the given identifier.
		/// </summary>
		/// <param name="pEntityId">The <c>Course</c> identifier.</param>
		/// <returns>The <c>Course</c> or <c>null</c>.</returns>
		public Course GetCourseWithId(int pEntityId)
		{
			Course rCrse = null;

			var vQuery = from pCourse in Courses.ToList()
						 where pCourse.EntityId == pEntityId
						 select pCourse;

			if (vQuery.Count() > 0)
				rCrse = vQuery.First();

			return rCrse;
		}

		/// <summary>
		/// Gets <c>AcademicTerm</c>s under this AcademicPlan with the given identifiers.
		/// </summary>
		/// <remarks>
		/// <c>AcademicTerm</c>s with valid identifiers but do not
		/// belong to this AcademicPlan are excluded.
		/// </remarks>
		/// <param name="pEntityIds">The <c>AcademicTerm</c>s identifiers.</param>
		/// <returns>The collection of <c>AcademicTerm</c>s.</returns>
		/// <since>1.0</since>
		public IList<AcademicTerm> GetTermsWithId(IList<int> pEntityIds)
		{
			var rTerms = new List<AcademicTerm>();

			var vQuery = from pTerm in Terms.ToList()
						 where pEntityIds.Contains(pTerm.EntityId)
						 select pTerm;

			rTerms = vQuery.ToList();
			return rTerms;
		}

		/// <summary>
		/// Gets the <c>CourseCategory</c>s under this AcademicPlan with the given identifier.
		/// </summary>
		/// <param name="pCategoryId">The <c>CourseCategory</c> identifier.</param>
		/// <returns>The <c>CourseCategory</c></returns>
		/// <since>1.0</since>
		public CourseCategory GetCategoryWithId(int pCategoryId)
		{
			CourseCategory rMod = null;

			SetDivisions();
			var vDivs = divisions.ToList();

			foreach (var vDiv in vDivs)
			{
				var vCatQuery = from pCat in vDiv.Categories.ToList()
								where pCat.EntityId == pCategoryId
								select pCat;

				if (vCatQuery.Count() > 0)
				{
					rMod = vCatQuery.First();
					break;
				}
			}

			return rMod;
		}

		internal void Set(double ? pCreditUnits)
		{
			var vErrs = Validate(pCreditUnits);

			if (vErrs.OuterErrors.Count > 0)
				throw new InvalidModelPropertiesException(DomainErrorCode.M_INVALID_ACADEMICPLAN_PROPERTIES, vErrs);

			if (pCreditUnits != null)
				Entity.Credit_Unit = (double)pCreditUnits;
		}

		internal void RemoveDivision(int pIndex)
		{
			Entity.DivisionEntities[pIndex].Trash();
		}

		internal void AddDivision(CourseDivision pDivision)
		{
			var vDivErrs = ValidateDivision(pDivision.EntityId, pDivision.Name);
			var vEnt = (CourseDivisionEntity)GetOtherModelEntity(pDivision);

			if (vDivErrs.OuterErrors.Count > 0)
			{
				var vErrs = new InvalidModelPropertiesErrorCollection("AcademicPlan");

				vErrs.AddInner("Divisions", vDivErrs);
				throw new InvalidModelPropertiesException(DomainErrorCode.M_INVALID_ACADEMICPLAN_DIVISIONS_PROPERTIES, vErrs);
			}

			Entity.AddDivisionEntity(vEnt);
			ResetDivisions();
		}

		internal InvalidModelPropertiesErrorCollection ValidateDivision(int pDivisionId, string pDivisionName)
		{
			var rErrs = new InvalidModelPropertiesErrorCollection("CourseDivision");

			SetDivisions();
			var vDivs = divisions.ToList();

			if (!pDivisionName.Equals(string.Empty)
				&& vDivs.Where(pDiv => (pDiv.Name.ToLower().Equals(pDivisionName.ToLower())
										&& pDiv.EntityId != pDivisionId)).Count() > 0)
			{
				rErrs.AddOuter("Name", InvalidModelPropertyErrorCode.NOT_UNIQUE);
			}

			return rErrs;
		}

		internal void AddTerm(AcademicTerm pTerm)
		{
			var vTermErrs = ValidateTerm(pTerm.EntityId, pTerm.Name, pTerm.OrderNum);
			var vEnt = (AcademicTermEntity)GetOtherModelEntity(pTerm);

			if (vTermErrs.OuterErrors.Count > 0)
			{
				var vErrs = new InvalidModelPropertiesErrorCollection("AcademicPlan");

				vErrs.AddInner("Terms", vTermErrs);
				throw new InvalidModelPropertiesException(DomainErrorCode.M_INVALID_ACADEMICPLAN_TERMS_PROPERTIES, vErrs);
			}

			Entity.AddTermEntity(vEnt);
			ResetTerms();
		}

		internal InvalidModelPropertiesErrorCollection ValidateTerm(int pTermId, string pTermName, int pTermOrderNum)
		{
			var rErrs = new InvalidModelPropertiesErrorCollection("AcademicTerm");

			SetTerms();
			var vTerms = terms.ToList();

			if (!pTermName.Equals(string.Empty)
				&& vTerms.Where(pT => (pT.Name.ToLower().Equals(pTermName.ToLower())
										&& pT.EntityId != pTermId)).Count() > 0)
			{
				rErrs.AddOuter("Name", InvalidModelPropertyErrorCode.NOT_UNIQUE);
			}

			if (pTermOrderNum > 0
				&& vTerms.Where(pT => (pT.OrderNum == pTermOrderNum
										&& pT.EntityId != pTermId)).Count() > 0)
			{
				rErrs.AddOuter("OrderNum", InvalidModelPropertyErrorCode.NOT_UNIQUE);
			}

			return rErrs;
		}

		internal void RemoveTerm(int pIndex)
		{
			Entity.TermEntities[pIndex].Trash();
		}

		internal void AddProgram(Program pProgram)
		{
			var vProgErrs = ValidateProgram(pProgram.EntityId, pProgram.Name);
			var vEnt = (ProgramEntity)GetOtherModelEntity(pProgram);

			if (vProgErrs.OuterErrors.Count > 0)
			{
				var vErrs = new InvalidModelPropertiesErrorCollection("AcademicPlan");

				vErrs.AddInner("Programs", vProgErrs);
				throw new InvalidModelPropertiesException(DomainErrorCode.M_INVALID_ACADEMICPLAN_PROGRAMS_PROPERTIES, vErrs);
			}

			Entity.AddProgramEntity(vEnt);
			ResetPrograms();
		}

		internal InvalidModelPropertiesErrorCollection ValidateProgram(int pProgramId, string pProgramName)
		{
			var rErrs = new InvalidModelPropertiesErrorCollection("Program");

			SetPrograms();
			var vProgs = programs.ToList();

			DomainMaster.Auth.EnterPrivelegedAuthorizationMode();
			if (!pProgramName.Equals(string.Empty)
				&& vProgs.Where(pProg => (pProg.Name.ToLower().Equals(pProgramName.ToLower())
											&& pProg.EntityId != pProgramId)).Count() > 0)
			{
				rErrs.AddOuter("Name", InvalidModelPropertyErrorCode.NOT_UNIQUE);
			}
			DomainMaster.Auth.ExitPrivelegedAuthorizationMode();

			return rErrs;
		}

		internal void AddCourse(Course pCourse)
		{
			var vCrseErrs = ValidateCourse(pCourse.EntityId, pCourse.Name, pCourse.Number);

			if (vCrseErrs.OuterErrors.Count > 0)
			{
				var vErrs = new InvalidModelPropertiesErrorCollection("AcademicPlan");

				vErrs.AddInner("Courses", vCrseErrs);
				throw new InvalidModelPropertiesException(DomainErrorCode.M_INVALID_ACADEMICPLAN_COURSES_PROPERTIES, vErrs);
			}
			
			Entity.AddCourseEntity((CourseEntity)GetOtherModelEntity(pCourse));
			ResetCourses();
		}

		internal InvalidModelPropertiesErrorCollection ValidateCourse(int pCourseId, string pCourseName, 
																		int pCourseNumber)
		{
			var rErrs = new InvalidModelPropertiesErrorCollection("Course");

			SetCourses();
			var vCrses = courses.ToList();

			DomainMaster.Auth.EnterPrivelegedAuthorizationMode();
			if (!pCourseName.Equals(string.Empty)
				&& vCrses.Where(pCrs => (pCrs.Name.ToLower().Equals(pCourseName.ToLower())
											&& pCrs.EntityId != pCourseId)).Count() > 0)
			{
				rErrs.AddOuter("Name", InvalidModelPropertyErrorCode.NOT_UNIQUE);
			}

			//if (pCourseNumber > 0
			//    && vCrses.Where(pCrs => (pCrs.Number == pCourseNumber
			//                                && pCrs.EntityId != pCourseId)).Count() > 0)
			//{
			//    rErrs.AddOuter("Number", InvalidModelPropertyErrorCode.NOT_UNIQUE);
			//}
			DomainMaster.Auth.ExitPrivelegedAuthorizationMode();

			return rErrs;
		}

		internal void AddSchedule(AcademicSchedule pSchedule)
		{
			var vSchErrs = ValidateProgram(pSchedule.EntityId, pSchedule.Name);

			if (vSchErrs.OuterErrors.Count > 0)
			{
				var vErrs = new InvalidModelPropertiesErrorCollection("AcademicPlan");

				vErrs.AddInner("Schedules", vSchErrs);
				throw new InvalidModelPropertiesException(DomainErrorCode.M_INVALID_ACADEMICPLAN_SCHEDULES_PROPERTIES, vErrs);
			}

			Entity.AddScheduleEntity((AcademicScheduleEntity)GetOtherModelEntity(pSchedule));
			ResetSchedules();
		}

		/// <summary>
		/// Validates possible values of a AcademicPlan's properties. <c>null</c> values are ignored.
		/// </summary>
		/// <param name="pCreditUnits">The common number of credits for most academic courses.</param>
		/// <returns>A Collection of validation errors.</returns>
		/// <seealso cref="M:ValidateCreditUnits"/>
		/// <since>1.0</since>
		public static InvalidModelPropertiesErrorCollection Validate(double ? pCreditUnits)
		{
			var rVal = new InvalidModelPropertiesErrorCollection("AcademicPlan");
			InvalidModelPropertyErrorCode vCode;

			if (pCreditUnits != null)
			{
				if (!(vCode = ValidateCreditUnits((double)pCreditUnits)).Equals(InvalidModelPropertyErrorCode.NONE))
					rVal.AddOuter("CreditUnits", vCode);
			}

			return rVal;
		}

		/// <summary>
		///  Validates a possible value of a AcademicPlan's common number 
		///  of credits for academic courses.
		/// </summary>
		/// <remarks>
		/// The value must be between <see cref="P:CREDITUNITS_MIN_VALUE"/>
		/// and <see cref="P:CREDITUNITS_MAX_VALUE"/> exclusive.
		/// </remarks>
		/// <param name="pVal">The value to validate.</param>
		/// <returns>A validation error code.</returns>
		/// <since>1.0</since>
		public static InvalidModelPropertyErrorCode ValidateCreditUnits(double pVal)
		{
			var rVal = InvalidModelPropertyErrorCode.NONE;

			if (pVal <= CREDITUNITS_MIN_VALUE)
				rVal = InvalidModelPropertyErrorCode.TOO_SMALL;
			else if (pVal >= CREDITUNITS_MAX_VALUE)
				rVal = InvalidModelPropertyErrorCode.TOO_LARGE;

			return rVal;
		}

		internal InvalidModelPropertiesErrorCollection ValidateSchedule(int pScheduleId, string pScheduleName)
		{
			var rErrs = new InvalidModelPropertiesErrorCollection("AcademicSchedule");

			SetSchedules();
			var vSchs = schedules.ToList();

			DomainMaster.Auth.EnterPrivelegedAuthorizationMode();
			if (!pScheduleName.Equals(string.Empty)
				&& vSchs.Where(pSch => (pSch.Name.ToLower().Equals(pScheduleName.ToLower())
											&& pSch.EntityId != pScheduleId)).Count() > 0)
			{
				rErrs.AddOuter("Name", InvalidModelPropertyErrorCode.NOT_UNIQUE);
			}
			DomainMaster.Auth.ExitPrivelegedAuthorizationMode();

			return rErrs;
		}
	}
}
